package com.kinderas.media
{
	import com.kinderas.events.ObjectEvent;
	import com.kinderas.events.VideostreamEvent;
	
	import flash.display.MovieClip;
	import flash.events.AsyncErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.NetStatusEvent;
	import flash.events.TimerEvent;
	import flash.media.SoundTransform;
	import flash.media.Video;
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.utils.Timer;
	
	/**
	 * The <i>VideostreamPlayer</i> class lets you create and play streaming video. The focus of this
	 * class is to function as an abstraction layer for all the classes and methods needed for playing
	 * streaming video.
	 * Supported file formats:
	 * <ul>
	 * <li>Flash Video (flv)</li>
	 * <li>MPEG H.264 (Video:base/mainline, high profile - Audio: AAC Main, AAC LC and SBR)</li>
	 * <ul>
	 * <li>m4v</li>
	 * <li>mov</li>
	 * <li>mp4</li>
	 * <li>m4a</li>
	 * <li>3gp</li>
	 * </ul>
	 * </ul> 
	 * @author Jørn Kinderås
	 * 
	 */	
	public class VideostreamPlayer extends EventDispatcher{
		
		private var _ns:NetStream;
		private var _videoURL:String;
		private var _autoplay:Boolean;
		private var _loadBuffer:uint;
		private var videoHolder:Video;
		private var _loaderBar:MovieClip;
		private var _playerProgressBar:MovieClip;
		private var _st:SoundTransform;
		private var _totalTime:Number = -1;
		private var _pTimer:Timer;
		private var _movieStopped:Boolean = true;
		
		/**
		 * Constructor.
		 *  
		 * @param videoUrl:String url to video file
		 * @param videoObject:Flash Video object
		 * @param [autoplayWhenLoaded]:Boolean(default true). Should movie autoplay when loaded
		 * @param [loaderBuffer]:uint denotes how much should be buffered(in percent) before playing
		 * @param [loaderBar]:MovieClip loader to show the load progress. Requires a MovieClip with 100 frames
		 * @param [playerProgressBar]:MovieClip shows the current progress of playback if supplied. Requires a MovieClip with 100 frames.
		 * 
		 * Note: If no loaderBar is supplied the VideostreamPlayer will return the current percentage loaded
		 * until it reaches the supplied value of the loaderBuffer. The event dispatched contains a property
		 * called <i>percentLoaded</i>. See VideostreamEvent for event documentation
		 */		
		public function VideostreamPlayer(videoUrl:String, videoObject:Video, 
									autoplayWhenLoaded:Boolean = true, loaderBuffer:uint = 100, 
									loaderBar:MovieClip = null,
									playerProgressBar:MovieClip = null){
			this._autoplay          = autoplayWhenLoaded;
			this._loadBuffer        = loaderBuffer;
			this.videoHolder        = videoObject;
			this._loaderBar         = loaderBar;
			this._playerProgressBar = playerProgressBar;
			this._videoURL          = videoUrl;
			this._st                = new SoundTransform();
			if(this._loaderBar != null){
				if(this._loaderBar.totalFrames == 100){
					startLoading();
				}else{
					trace("VideostreamPlayer Error: Provided loaderBar("
					+this._loaderBar.name+") does not have 100 frames");
					throw new Error("LoaderBar must have 100 frames!");
				}
			}else{
				startLoading();
			}
			//verify the playbackbar
			if(this._playerProgressBar != null){
				if(this._playerProgressBar.totalFrames != 100){
					trace("VideostreamPlayer Error: Provided playerBar("
					+this._playerProgressBar.name+") does not have 100 frames");
					throw new Error("PlayerBar must have 100 frames!");
				}
			}
		}
		//Getters
		/**
		 * 
		 * @return An object containing the current position of the playhead in seconds
		 * and the totaltime of the movie. 
		 * Object.totaltime
		 * Object.currenttime
		 * 
		 * Note: totaltime will return 0 untill metadata is available
		 *
		 */		
		public function get playbackProgress():Array{
			var times:Array = new Array();
			if(this._totalTime != -1){
				times['totaltime']   = this._totalTime;
				times['currenttime'] = this._ns.time;	
			}else{
				times['totaltime']   = 0;
				times['currenttime'] = this._ns.time;
			}
			return times;
		}
		//Setters
		/**
		 * 
		 * @param vol:Number which sets the sound volume. 0 is muted 1 is 100% volume
		 * 
		 */		
		public function set volume(vol:Number):void{
			this._st.volume         = vol;
			this._ns.soundTransform = this._st;
		}
		//Public functions
		/**
		 * Toggles the sound for the video on/off.
		 * 
		 */		
		public function toggleMute():void{
			if(this._st.volume == 0){
				this._st.volume = 1;
			}else{
				this._st.volume = 0;
			}
			this._ns.soundTransform = this._st;
		}
		/**
		 * Toggles between play/pause of the playing movie. 
		 * If the movie has finished playing(at last frame), toggling this will restart the movie.
		 * 
		 */		
		public function togglePause():void{
			//If movie is done playing, reset and play again
			if(playheadPos() >= 100){
				this._ns.seek(0);
				this._ns.togglePause();
			}
			//Toggle pause
			this._ns.togglePause();
			//if movie stopped, stop the timer
			if(this._movieStopped){
				this._movieStopped = false;
				if(this._playerProgressBar != null){
					this._pTimer.start();	
				}
			}else{
				this._movieStopped = true;
				this._pTimer.stop();
			}
		}
		/**
		 * 
		 * @param time:Number moves the playhead to the closest matching keyframe.
		 * 
		 */		
		public function seek(time:Number):void{
			this._ns.seek(time);
		}
		/**
		 * Close the video and stop downloading 
		 * 
		 */		
		public function close():void{
			this._pTimer.stop();
			this._ns.close();
		}
		
		/*PRIVATE FUNCTIONS*/
		//Start loading
		private function startLoading():void{
			var nc:NetConnection = new NetConnection();
			nc.connect(null);
			this._ns = new NetStream(nc);
			this._ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, syncErrorHandler);
			this._ns.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
			this._ns.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
			//Metadata
			var customClient:Object = new Object();
			customClient.onMetaData = metaDataHandler;
			this._ns.client = customClient;
			//Start streaming
			this._ns.play(this._videoURL);
			this._ns.togglePause();
			//Attach video object
			this.videoHolder.attachNetStream(this._ns);
			//Load progress
			var lTimer:Timer = new Timer(100,0);
			lTimer.addEventListener(TimerEvent.TIMER, videoLoadingHandler);
			lTimer.start();
			//Playhead progress
			this._pTimer = new Timer(500,0);
			this._pTimer.addEventListener(TimerEvent.TIMER, trackProgressHandler);
		}
		//Handle error
		private function syncErrorHandler(evt:AsyncErrorEvent):void{
			trace("VideostreamPlayer Error: ASync error "+evt.text);
		}
		private function ioErrorHandler(evt:IOErrorEvent):void{
			trace("VideostreamPlayer Error: Error loading file");
		}
		//Handle net status
		private function netStatusHandler(evt:NetStatusEvent):void{
			switch(evt.info.code){
				case "NetStream.Buffer.Flush":
				if(this._ns.time >= this._totalTime){
					this._pTimer.stop();
					this._movieStopped = true;
					dispatchEvent(new Event(VideostreamEvent.PLAYING_DONE));
				}
				break;
				case "NetStream.Play.Start":
				dispatchEvent(new Event(VideostreamEvent.STREAM_START));
				break;
			}
		}
		//Handle loading
		private function videoLoadingHandler(evt:TimerEvent):void{
			var userSpesBuffer:Number = this._ns.bytesTotal*this._loadBuffer/100;
			var percentComplete:uint  = Math.round(this._ns.bytesLoaded*100/userSpesBuffer);
			if(this._loaderBar != null){
				this._loaderBar.gotoAndStop(percentComplete);
			}else{
				var progress:Object      = new Object();
				progress.percentLoaded   = percentComplete;
				var objEvt:ObjectEvent   = new ObjectEvent(VideostreamEvent.STREAM_LOADING,progress);
				dispatchEvent(objEvt);
			}
			//Detect whether to start playing
			if(percentComplete >= 100){
				if(this._autoplay){
					evt.currentTarget.stop();
					//this._ns.togglePause();
					togglePause();
					dispatchEvent(new Event(VideostreamEvent.PLAYING_START));
				}
			}else{
				this._ns.pause();
			}	
		}
		//Get the percentage of playback
		private function playheadPos():Number{
			if(this._totalTime != -1){
				return this._ns.time*100/this._totalTime;	
			}else{
				return 0;
			}
		}
		//Handle metadata
		private function metaDataHandler(infoObject:Object):void{
		    this._totalTime = parseFloat(infoObject["duration"]);
		}
		//Track playing progress
		private function trackProgressHandler(evt:TimerEvent):void{
			this._playerProgressBar.gotoAndStop(Math.round(playheadPos()));	
		}
	}
}